using LightCAD.MathLib;
using System;
using System.Collections;
using System.Collections.Generic;


namespace LightCAD.Three
{
    public class SphereGeometry : BufferGeometry
    {
        public class Parameters
        {
            public double radius;
            public int widthSegments;
            public int heightSegments;
            public double phiStart;
            public double phiLength;
            public double thetaStart;
            public double thetaLength;

        }
        #region Properties

        public Parameters parameters;

        #endregion

        #region constructor
        public SphereGeometry(double radius = 1, int widthSegments = 32, int heightSegments = 16, double phiStart = 0, double phiLength = Math.PI * 2, double thetaStart = 0, double thetaLength = Math.PI) : base()
        {
            this.type = "SphereGeometry";
            this.parameters = new Parameters
            {
                radius = radius,
                widthSegments = widthSegments,
                heightSegments = heightSegments,
                phiStart = phiStart,
                phiLength = phiLength,
                thetaStart = thetaStart,
                thetaLength = thetaLength

            };
            widthSegments = (int)Math.Max(3, widthSegments);
            heightSegments = (int)Math.Max(2, heightSegments);
            var thetaEnd = Math.Min(thetaStart + thetaLength, MathEx.PI);
            var index = 0;
            var grid = new ListEx<ListEx<int>>();
            var vertex = new Vector3();
            var normal = new Vector3();
            // buffers
            var indices = new ListEx<int>();
            var vertices = new ListEx<double>();
            var normals = new ListEx<double>();
            var uvs = new ListEx<double>();
            // generate vertices, normals and uvs
            for (int iy = 0; iy <= heightSegments; iy++)
            {
                var verticesRow = new ListEx<int>();
                double v = (double)iy / heightSegments;
                // special case for the poles
                var uOffset = 0.0;
                if (iy == 0 && thetaStart == 0)
                {
                    uOffset = 0.5 / widthSegments;
                }
                else if (iy == heightSegments && thetaEnd == MathEx.PI)
                {
                    uOffset = -0.5 / widthSegments;
                }
                for (int ix = 0; ix <= widthSegments; ix++)
                {
                    var u = (double)ix / widthSegments;
                    // vertex
                    vertex.X = -radius * Math.Cos(phiStart + u * phiLength) * Math.Sin(thetaStart + v * thetaLength);
                    vertex.Y = radius * Math.Cos(thetaStart + v * thetaLength);
                    vertex.Z = radius * Math.Sin(phiStart + u * phiLength) * Math.Sin(thetaStart + v * thetaLength);
                    vertices.Push(vertex.X, vertex.Y, vertex.Z);
                    // normal
                    normal.Copy(vertex).Normalize();
                    normals.Push(normal.X, normal.Y, normal.Z);
                    // uv
                    uvs.Push(u + uOffset, 1 - v);
                    verticesRow.Push(index++);
                }
                grid.Push(verticesRow);
            }
            // indices
            for (int iy = 0; iy < heightSegments; iy++)
            {
                for (int ix = 0; ix < widthSegments; ix++)
                {
                    var a = grid[iy][ix + 1];
                    var b = grid[iy][ix];
                    var c = grid[iy + 1][ix];
                    var d = grid[iy + 1][ix + 1];
                    if (iy != 0 || thetaStart > 0) indices.Push(a, b, d);
                    if (iy != heightSegments - 1 || thetaEnd < MathEx.PI) indices.Push(b, c, d);
                }
            }
            // build geometry
            this.setIndex(indices.ToArray());
            this.setAttribute("position", new Float32BufferAttribute(vertices.ToArray(), 3));
            this.setAttribute("normal", new Float32BufferAttribute(normals.ToArray(), 3));
            this.setAttribute("uv", new Float32BufferAttribute(uvs.ToArray(), 2));
        }
        #endregion

        #region methods
        public SphereGeometry copy(SphereGeometry source)
        {
            base.copy(source);
            this.parameters = new Parameters()
            {
                radius = source.parameters.radius,
                widthSegments = source.parameters.widthSegments,
                heightSegments = source.parameters.heightSegments,
                phiStart = source.parameters.phiStart,
                phiLength = source.parameters.phiLength,
                thetaStart = source.parameters.thetaStart,
                thetaLength = source.parameters.thetaLength,
            };
            return this;
        }
        #endregion

    }
}
